package com.netstar.request;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.EntityTemplate;

import com.netstar.util.EvtLog;

import android.os.Handler;
import android.os.Looper;

public abstract class BaseRequest extends HttpRequest {

	private static final String TAG = "BaseRequest";

	public static final String FAIL_MSG_PARSE_SERVER_ERROR = "Parse ServerError object error";
	public static final String FAIL_MSG_PARSE_JSON = "Parse JSON object error";
	public static final String FAIL_MSG_HTTP_IO = "HTTP request IO error";
	public static final String FAIL_MSG_TIMEOUT = "HTTP timeout error";

	private static final int DEFAULT_REQUEST_TIMEOUT = 30000;

	/**
	 * The request succeeds and receiving response message has completed.
	 */
	public static final int HTTP_COMPLETE = 12;

	/**
	 * The request has fails.
	 */
	public static final int HTTP_FAILED_NETWORK = 13;

	public static final int HTTP_FAILED_URI_INVALID = 14;

	/*
	 * If the developer construct this object in non-main thread, this statement
	 * will throw an exception.
	 */
	// protected final Handler mHandler = new Handler();

	/*
	 * Make sure this handler is attached in the main thread.
	 */
	protected static final Handler sHandler = new Handler(Looper.getMainLooper());

	private boolean mIsRequestMade = false;

	/*
	 * Declare canceled field as 'volatile' to keep up its visibility in all
	 * related threads.
	 */
	private volatile boolean canceled = false;

	public BaseRequest() {
		super();
	}

	protected void markCanceled() {
		canceled = true;
	}

	protected boolean isCanceled() {
		return canceled;
	}

	protected Runnable mTimeoutRunnable = new Runnable() {

		public void run() {
			markCanceled();
			onTimeout();
		}
	};

	protected void addOAuthHeader() {
	}

	/**
	 * Determine whether this request needs OAuth verifying.
	 * <p>
	 * Return false to disable.
	 */
	protected boolean needOAuth() {
		return true;
	}

	protected boolean needSessionId() {
		return true;
	}

	/**
	 * Determine whether parse the response to a json object.
	 * <p>
	 * Return false to disable.
	 */
	protected boolean parseJsonRequired() {
		return true;
	}

	/**
	 * Sets the value of the specified request header field.
	 * <p>
	 * Must be called before {@link #makeRequest()}.
	 */
	public final void setRequestHeader(String key, String value) {
		if (mIsRequestMade) {
			throw new IllegalStateException("Must called before makeRequest()");
		}
		getRequest().setHeader(key, value);
	}

	/**
	 * Adds the given property to the request header. The header will be
	 * appended to the end of the list.
	 * <p>
	 * Must be called before {@link #makeRequest()}.
	 */
	public final void addRequestHeader(String key, String value) {
		if (mIsRequestMade) {
			throw new IllegalStateException("Must called before makeRequest()");
		}
		getRequest().addHeader(key, value);
	}

	public final void makeRequest() {
		mIsRequestMade = true;
		/*
		 * This timeout runnable must be enqueued to the message queue before
		 * executing the request runnable.
		 * 
		 * Make sure when the http response ends the time runnable is already
		 * there.
		 */
		sHandler.postDelayed(mTimeoutRunnable, getDefaultTimeout());
		sExecutor.execute(new Runnable() {

			public void run() {
				if (needOAuth()) {
					addOAuthHeader();
				}

				if (HttpRequest.POST.equalsIgnoreCase(getMethod())) {
					HttpPost post = (HttpPost) getRequest();
					String type = getMIMEType();
					byte[] uploadData = getUploadData();

					if (null == type || null == uploadData) {
						List<NameValuePair> params = toList(getData());
						if (params.size() > 0) {
							UrlEncodedFormEntity entity;
							try {
								entity = new UrlEncodedFormEntity(params, getPostUrlEncoder());
								post.setEntity(entity);
							} catch (UnsupportedEncodingException e) {
							}

						}
					} else {
						/*
						 * Well, it is a upload file request, so translate the
						 * entity.
						 */
						post.setHeader("Connection", "Keep-Alive");
						post.setHeader("Content-Type", "multipart/form-data; boundary=" + MultiProducer.BOUNDARY);
						HttpEntity entity = new EntityTemplate(new MultiProducer(uploadData, type, getData()));
						post.setEntity(entity);
					}
				}
				try {
					excute();
					onStateChanged(BaseRequest.this, HTTP_COMPLETE);
				} catch (IOException e) {
					EvtLog.d(TAG, e.toString());
					try {
						HttpUriRequest request = getRequest();
						if (null != request) {
							request.abort();
						}
					} catch (UnsupportedOperationException ex) {
					}
					onStateChanged(BaseRequest.this, HTTP_FAILED_NETWORK);
				} catch (URISyntaxException e) {
					EvtLog.d(TAG, e.toString());
					onStateChanged(BaseRequest.this, HTTP_FAILED_URI_INVALID);
				} catch (IllegalStateException ex) {
					EvtLog.d(TAG, ex.toString());
					onStateChanged(BaseRequest.this, HTTP_FAILED_URI_INVALID);
				}

			}
		});
	}

	protected void onStateChanged(final HttpRequest request, final int state) {
		sHandler.post(new Runnable() {

			public void run() {
				if (isCanceled()) {
					return;
				}
				if (HTTP_COMPLETE == state) {
					onHttpComplete(request);
					request.releaseResources();
				} else if (HTTP_FAILED_NETWORK == state) {
					onHttpFailNetwork(request);
					request.releaseResources();
				} else if (HTTP_FAILED_URI_INVALID == state) {
					onHttpFailURIInvalid(request);
					request.releaseResources();
				}

				sHandler.removeCallbacks(mTimeoutRunnable);
			}
		});
	}

	/**
	 * The MIME type of the file. Simply returns null here.
	 * <p>
	 * Used to upload file.
	 */
	public String getMIMEType() {
		return null;
	}

	/**
	 * The raw data of the file. Simply returns null here.
	 * <p>
	 * Used to upload file.
	 */
	public byte[] getUploadData() {
		return null;
	}

	protected int getDefaultTimeout() {
		return DEFAULT_REQUEST_TIMEOUT;
	}

	/**
	 * Called when the request timeouts. Override {@link #getDefaultTimeout()}
	 * to change.
	 * <p>
	 * <b>Runs in UI thread.
	 */
	protected void onTimeout() {
		onFail("网络不稳定，连接服务器超时");
	}

	/**
	 * Called when the http request failed.
	 * <p>
	 * <b>Runs in UI thread.
	 */
	protected void onHttpFailNetwork(HttpRequest request) {
		onFail("网络错误，请检查网络设置");
	}

	/**
	 * Called when the http request failed.
	 * <p>
	 * <b>Runs in UI thread.
	 */
	protected void onHttpFailURIInvalid(HttpRequest request) {
		onFail("服务器内部错误");
	}

	/**
	 * Called when the response has ended.
	 * <p>
	 * <b>Runs in UI thread.
	 */
	protected abstract void onHttpComplete(HttpRequest request);

	public abstract void onFail(String msg);

	public String getPostUrlEncoder() {
		return "utf-8";
	}
}
